export interface PageQueryParams {
  page: number;

  size: number;

  prop?: string;

  order?: 'asc' | 'desc';

  [name: string]: any;
}

export interface PageResponse<T> {
  empty: boolean;

  first: boolean;

  last: boolean;

  number: number;

  numberOfElements: number;

  size: number;

  totalElements: number;

  totalPages: number;

  content: T[];

}

export interface PageDataConfig {
  size: number;
}

export type PageQueryFn<T> = (params: PageQueryParams) => Promise<PageResponse<T>>

export class PageData<T> {
  constructor (queryFn: PageQueryFn<T>, config: Partial<PageDataConfig> = {}) {
    this.queryFn = queryFn
    Object.assign(this.config, config)
    this.params = { page: 0, size: this.config.size }
    this.size = this.config.size
  }

  config: PageDataConfig = {
    size: 10
  }

  page = 0

  size = 10

  total = 0

  loading = false

  params!: PageQueryParams

  rows: T[] = []

  hasMore = true

  empty = false

  hasError = false

  queryFn!: PageQueryFn<T>

  _load (params?: PageQueryParams, reserved = false): Promise<any> {
    if (params) this.params = params
    this.loading = true
    if (!reserved) this.rows = []
    return this.queryFn(this.params).then(data => {
      this.page = data?.numberOfElements
      this.size = data?.size
      this.total = data?.totalElements
      this.rows = reserved ? merge(this.rows, data?.content) : data?.content
      this.hasMore = !data?.last
      this.empty = data?.empty
      this.hasError = false
    }).catch(e => {
      this.hasError = true
      throw e
    }).finally(() => {
      this.loading = false
    })

    function merge (source: any[] = [], target: any[] = [], key = 'id') {
      const set = new Set(source.map(v => v.id))
      return [...source, ...target.filter(v => !set.has(v.id))]
    }
  }

  load (params: Partial<PageQueryParams> = {}) {
    this._load(Object.assign({ page: 0, size: this.params.size }, params))
  }

  sort (params: Partial<PageQueryParams> = {}) {
    this._load(Object.assign({}, this.params, { page: 0, size: this.params.size }, params))
  }

  filter (params: Partial<PageQueryParams> = {}) {
    const { prop, order } = this.params
    this._load(Object.assign({ page: 0, size: this.params.size, prop, order }, params))
  }

  gotoPage (page = 0) {
    this._load(Object.assign({}, this.params, { page: page }))
  }

  loadNext (): Promise<any> {
    if (!this.hasMore || this.hasError || this.loading) return Promise.resolve()
    return this._load(Object.assign({}, this.params, { page: this.page === 0 ? 0 : this.params.page + 1 }), true)
  }

  reload (reset = true) {
    if (reset) {
      this.params.page = 0
    }
    this._load()
  }
}
